#include <stdio.h>

#define HH(a) ((a) >> 6)
#define HL(a) (((a) & 0x30) >> 4)
#define LH(a) (((a) & 0x0c) >> 2)
#define LL(a) ((a) & 0x03)

#define BYTE unsigned char

BYTE mem[0xff], pc = 0;
BYTE overflow, zero;
BYTE reg[4];

void loadMem(char* filename) {
    FILE* fin = fopen(filename, "r");
    for (int i = 0; i & 0xff; ++ i) {
        char c = getc(fin);
        mem[i] = c;
    }
}

void run() {
    while (1) {
        BYTE I = mem[pc];
        switch (HH(I)) {
            case 0: { // CONTROL TRANSFER
                BYTE mode = HL(I);
                if (mode == 0) break; // NOP
                BYTE doJmp = 0;
                if (mode == 1 && overflow || // JO
                    mode == 2 && zero || // JZ
                    mode == 3) doJmp = 1; // JMP
                if (doJmp) {
                    BYTE absolute = LH(I);
                    BYTE addr = reg[LL(I)];
                    if (absolute) pc = pc + addr;
                    else pc = addr;
                    continue; // DO jump
                }
                break;
            }
            case 1: { // SET
                BYTE immed = (I & 0x3c) >> 4;
                BYTE r1 = LL(I);
                reg[r1] = reg[r1] & 0xf0 | immed;
                break;
            }
            case 2: { // IO
                BYTE device = (I & 0x20) >> 5;
                BYTE write = (I & 0x10) >> 4;
                BYTE r2 = reg[LH(I)];
                BYTE r1 = reg[LL(I)];
                if (device == 0) // memory
                    if (write) mem[r2] = r1;
                    else r1 = mem[r2];
                else
                    if (write) printf("Target 0x%02hhx, Output 0x%02hhx.\n", r2, r1);
                    else { printf("Target 0x%02hhx, Input: ", r2); scanf("%hhx", reg + LL(I)); }
                break;
            }
            case 3: { // ARITHMETIC & LOGIC
                BYTE sel1 = HL(I);
                BYTE r1 = LL(I), r2 = LH(I);
                switch (sel1) {
                    case 0: // ADD
                        overflow = (((int)(reg[r1]) + (int)(reg[r2])) & 256) != 0;
                        reg[r1] += reg[r2];
                        break;
                    case 1: // NAND
                        reg[r1] = ! (reg[r1] & reg[r2]);
                        overflow = 0;
                        break;
                    case 2: // XOR
                        reg[r1] ^= reg[r2];
                        overflow = 0;
                        break;
                    case 3: // OTHERS
                        switch (r2) {
                            case 0: // NEG
                                reg[r1] = (~ reg[r1]) + 1;
                                overflow = 0;
                            case 1: // SWP
                                reg[r1] = (reg[r1] & 0xf0) >> 4 | (reg[r1] & 0x0f) << 4;
                                overflow = 0;
                            case 2: // SHR
                                overflow = reg[r1] & 0x01;
                                reg[r1] >>= 1;
                            case 3: // SND
                                overflow = 0;
                        }
                        break;
                }
                zero = reg[r1] == 0;
                break;
            }
        }
        pc += 1;
    }
}

int main (int argc, char* argv[]) {
    if (argc != 1) {
        printf("Unsupported arguments.");
        return 0;
    }
    printf("Loading...");
    loadMem(argv[0]);
    run();
    return 0;
}